﻿

namespace EveKillboard.Core.Parsers
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Globalization;
    using System.Linq;
    using System.Text;
    using System.Text.RegularExpressions;
    using Exceptions;
    using Services;
    using System.Diagnostics;
    
    public sealed class KillmailTextParser : KillmailParserBase
    {
        #region Constants
        /// <summary>
        /// (Copy)
        /// </summary>
        private const string BLUEPRINT_COPY = "(Copy)";
        /// <summary>
        /// (Implant)
        /// </summary>
        const string IMPLANT = "(Implant)";
        /// <summary>
        /// (laid the final blow)
        /// </summary>
        const string FINAL_BLOW = "(laid the final blow)";
        /// <summary>
        /// (Cargo)
        /// </summary>
        const string CARGO = "(Cargo)";
        /// <summary>
        /// (Drone Bay)
        /// </summary>
        const string DRONE_BAY = "(Drone Bay)";
        /// <summary>
        /// yyyy.MM.dd HH:mm:ss
        /// </summary>
        const string DATETIME_FORMAT = "yyyy.MM.dd HH:mm:ss";
        /// <summary>
        /// Qty:
        /// </summary>
        const string KEY_QUANTITY = "Qty:";
        /// <summary>
        /// Victim:
        /// </summary>
        const string KEY_VICTIM = "Victim";
        /// <summary>
        /// Corp:
        /// </summary>
        const string KEY_CORPORATION = "Corp";
        /// <summary>
        /// Alliance:
        /// </summary>
        const string KEY_ALLIANCE = "Alliance";
        /// <summary>
        /// Faction:
        /// </summary>
        const string KEY_FACTION = "Faction";
        /// <summary>
        /// Destroyed:
        /// </summary>
        const string KEY_DESTROYED_SHIP = "Destroyed";
        /// <summary>
        /// System:
        /// </summary>
        const string KEY_SYSTEM = "System";
        /// <summary>
        /// Security:
        /// </summary>
        const string KEY_SECURITY_STATUS = "Security";
        /// <summary>
        /// Damage Taken:
        /// </summary>
        const string KEY_DAMAGE_TAKEN = "Damage Taken";
        /// <summary>
        /// Involved parties:
        /// </summary>
        const string KEY_INVOLVED = "Involved parties:";
        /// <summary>
        /// Name:
        /// </summary>
        const string KEY_NAME = "Name";
        /// <summary>
        /// Ship:
        /// </summary>
        const string KEY_SHIP = "Ship";
        /// <summary>
        /// Weapon:
        /// </summary>
        const string KEY_WEAPON = "Weapon";
        /// <summary>
        /// Damage Done:
        /// </summary>
        const string KEY_DAMAGE_DONE = "Damage Done";
        /// <summary>
        /// Destroyed items:
        /// </summary>
        const string KEY_DESTROYED_ITEM = "Destroyed items:";
        /// <summary>
        /// Dropped items:
        /// </summary>
        const string KEY_DROPPED_ITEM = "Dropped items:";
        /// <summary>
        /// ':'
        /// </summary>
        const char KEY_SEPARATOR = ':';
        /// <summary>
        /// Control Tower
        /// </summary>
        const string KEY_TOWER = "Control Tower";
        /// <summary>
        /// Warp Disruptor
        /// </summary>
        const string KEY_WARP_DISRUPTOR = "Warp Disruptor";
        /// <summary>
        /// Moon
        /// </summary>
        const string KEY_MOON = "Moon";
        /// <summary>
        /// None
        /// </summary>
        const string KEY_NONE = "None";
        /// <summary>
        /// Unknown
        /// </summary>
        private const string KEY_UNKNOWN = "Unknown";
        /// <summary>
        /// Sentry Gun
        /// </summary>
        const string KEY_SENTRY = "Sentry Gun";
        /// <summary>
        /// Battery
        /// </summary>
        private const string KEY_BATTERY = "Battery";
        /// <summary>
        /// Involved parties regular expression
        /// </summary>
        private const string REGEX_INVOLVED = @"(?:Name:\s)(?<Name>.*)(?:\r\n)(?:Security:\s)(?<Security>.*)(?:\r\n)(?:Corp:\s)(?<Corp>.*)(?:\r\n)(?:Alliance:\s)(?<Alliance>.*)(?:\r\n)(?:Faction:\s)(?<Faction>.*)(?:\r\n)(?:Ship:\s)(?<Ship>.*)(?:\r\n)(?:Weapon:\s)(?<Weapon>.*)(?:\r\n)(?:Damage Done:\s)(?<damage>.*)(?:\r\n)|(?:Name:\s)(?<Name>.*)(?:\r\n)(?:Damage Done:\s)(?<damage>.*)(?:\r\n)";

        #endregion

        private readonly TraceSource _trace = new TraceSource("KillmailTextParser");

        public KillmailTextParser() : base(){}

        public override IFeedKillmail Parse(string content)
        {
            _trace.TraceEvent(TraceEventType.Verbose, 0, "Parsing killmail content starting");

            if (string.IsNullOrEmpty(content))
                throw new ArgumentNullException("content");

            var lines = content.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries);
            if (lines == null || lines.Length == 0)
                throw new ParsingException("killmail content is empty");

            DateTime date;
            if (!DateTime.TryParse(lines[0], CultureInfo.InvariantCulture, DateTimeStyles.None, out date))
                throw new ParsingException("Killmail date format invalid");

            var victim = Append(lines, KEY_VICTIM);
            var corp = Append(lines, KEY_CORPORATION);
            var alliance = Append(lines, KEY_ALLIANCE);
            var faction = Append(lines, KEY_FACTION);
            var destroyed = Append(lines, KEY_DESTROYED_SHIP);
            var system = Append(lines, KEY_SYSTEM);
            var damage = Append(lines, KEY_DAMAGE_TAKEN);
            var moon = Append(lines, KEY_MOON);

            var killmail = new FeedKillmail()
                               {
                                   Date = date,
                                   VictimName = string.IsNullOrEmpty(victim) ? moon : victim,
                                   VictimCorporationName = corp,
                                   VictimAllianceName = alliance,
                                   VictimFactionName = faction,
                                   SystemName = system,
                                   DamageReceived = int.Parse(damage),
                                   VictimShipName = destroyed,
                                   Involved = new List<IFeedKillmailInvolved>(),
                                   Items = new List<IFeedKillmailItem>()
                               };
        
            if(string.IsNullOrEmpty(killmail.VictimName))
                throw new ParsingException("Invalid victim name");
            if(string.IsNullOrEmpty(killmail.VictimCorporationName))
                throw new ParsingException("Invalid corporation name");
            if(string.IsNullOrEmpty(destroyed))
                throw new ParsingException("Invalid destroyed ship name");
            if(string.IsNullOrEmpty(system))
                throw new ParsingException("Invalid system name");
            if(killmail.DamageReceived < 0 )
                throw new ParsingException("Invalid damage received value");

            // Involved parties
            var matches = Regex.Matches(content, REGEX_INVOLVED);
            foreach (Match match in matches)
            {
                if (!match.Success) continue;
                var name = match.Groups["Name"].Value.Replace(FINAL_BLOW, null);
                bool isNpc = match.Groups["Name"].Value.Contains("/");
                bool isStructure = name.Contains(KEY_TOWER) || name.Contains(KEY_WARP_DISRUPTOR) || name.Contains(KEY_SENTRY) || name.Contains(KEY_BATTERY);

                if(isNpc && !isStructure)
                {
                    var parts = name.Split('/');
                    var npcEntity = parts[0].Trim();
                    var npcCorpOrFaction = parts[1].Trim();

                    var npc = new FeedKillmailInvolved()
                    {
                        CharacterName = npcEntity,
                        CorporationName = string.Equals(KEY_NONE, npcCorpOrFaction) ? null : npcCorpOrFaction,
                        SecurityStatus = string.IsNullOrEmpty(match.Groups["Security"].Value) ? 0 : float.Parse(match.Groups["Security"].Value.Replace(',', '.'), CultureInfo.InvariantCulture),
                        DamageDone = string.IsNullOrEmpty(match.Groups["damage"].Value) ? 0 : int.Parse(match.Groups["damage"].Value, CultureInfo.InvariantCulture),
                        IsFinalBlow = match.Groups["Name"].Value.Contains(FINAL_BLOW),
                        ShipName = npcEntity,
                    };
                    ((IList) killmail.Involved).Add(npc);
                }
                else
                {
                    var invol = new FeedKillmailInvolved()
                    {
                        CharacterName = isStructure ? name.Split('/')[0].Trim() : name,
                        IsFinalBlow = match.Groups["Name"].Value.Contains(FINAL_BLOW),
                        DamageDone = string.IsNullOrEmpty(match.Groups["damage"].Value) ? 0 : int.Parse(match.Groups["damage"].Value, CultureInfo.InvariantCulture),
                        AllianceName = string.Equals(KEY_NONE, match.Groups["Alliance"].Value) ? null : match.Groups["Alliance"].Value,
                        CorporationName = isStructure ? name.Split('/')[1].Trim() :  match.Groups["Corp"].Value,
                        FactionName = string.Equals(KEY_NONE, match.Groups["Faction"].Value) || string.Equals(KEY_UNKNOWN, match.Groups["Faction"].Value) ? null : match.Groups["Faction"].Value,
                        SecurityStatus = string.IsNullOrEmpty(match.Groups["Security"].Value) ? 0 : float.Parse(match.Groups["Security"].Value.Replace(',', '.'), CultureInfo.InvariantCulture),
                        ShipName = isStructure ? name.Split('/')[0].Trim() : string.IsNullOrEmpty(match.Groups["Ship"].Value) ? null : match.Groups["Ship"].Value,
                        WeaponName = isStructure ? null : string.IsNullOrEmpty(match.Groups["Weapon"].Value) ? null : match.Groups["Weapon"].Value,
                    };
                    ((IList) killmail.Involved).Add(invol);
                }
            }

            // Items destroyed or dropped
            ((IList)killmail.Items).Add(new FeedKillmailItem() { IsDestroyed = true, ItemName = killmail.VictimShipName, Quantity = 1 });
            
            bool hasDestroyedItems = Array.IndexOf(lines, KEY_DESTROYED_ITEM) != -1;
            bool hasDroppedItems = Array.IndexOf(lines, KEY_DROPPED_ITEM) != -1;
            
            if (!hasDestroyedItems && !hasDroppedItems) return killmail;

            var destroyedIndex = Array.IndexOf(lines, KEY_DESTROYED_ITEM);
            var droppedIndex = Array.IndexOf(lines, KEY_DROPPED_ITEM);
            if (droppedIndex == -1)
                droppedIndex = lines.Length;

            if(hasDestroyedItems)
                for (int i = destroyedIndex + 1; i < droppedIndex; i++)
                {
                    var parts = lines[i].Split(',');
                    var item = new FeedKillmailItem()
                                   {
                                       IsDestroyed = true,
                                       IsCargo = lines[i].Contains(CARGO),
                                       IsImplant = lines[i].Contains(IMPLANT),
                                       IsDroneBay = lines[i].Contains(DRONE_BAY),
                                       IsCopy = lines[i].Contains(BLUEPRINT_COPY),
                                       ItemName = GetItemName(parts[0]),
                                       Quantity = lines[i].Contains(KEY_QUANTITY)
                                                      ? GetQuantity(parts[1].Split(new[] {KEY_QUANTITY}, StringSplitOptions.RemoveEmptyEntries)[1])
                                                      : 1
                                   };
                    ((IList) killmail.Items).Add(item);
                }

            if (droppedIndex != lines.Length) // EOF ?
                for (int i = droppedIndex + 1; i < lines.Length; i++)
                {
                    var parts = lines[i].Split(',');
                    var item = new FeedKillmailItem()
                                   {
                                       IsDropped = true,
                                       IsCargo = lines[i].Contains(CARGO),
                                       IsImplant = lines[i].Contains(IMPLANT),
                                       IsDroneBay = lines[i].Contains(DRONE_BAY),
                                       IsCopy = lines[i].Contains(BLUEPRINT_COPY),
                                       ItemName = GetItemName(parts[0]),
                                       Quantity = lines[i].Contains(KEY_QUANTITY)
                                                      ? GetQuantity(parts[1].Split(new[] {KEY_QUANTITY}, StringSplitOptions.RemoveEmptyEntries)[1])
                                                      : 1
                                   };
                    ((IList)killmail.Items).Add(item);
                }

            _trace.TraceEvent(TraceEventType.Verbose, 0, "Parsing killmail content finished");
            return killmail;
        }

        static string GetItemName(string value)
        {
            return value.Replace(CARGO, null).Replace(IMPLANT, null).Replace(DRONE_BAY, null).Replace(BLUEPRINT_COPY, null).Trim();
        }

        static int GetQuantity(string value)
        {
            return int.Parse( GetItemName(value), CultureInfo.InvariantCulture);
        }
        
        static string Append(IEnumerable<string> lines, string key, StringBuilder sb = null)
        {
            foreach (var pairs in lines
                .Select(line => line.Split(new[] { KEY_SEPARATOR }, StringSplitOptions.RemoveEmptyEntries))
                .Where(pairs => string.Equals(pairs[0], key, StringComparison.InvariantCultureIgnoreCase)))
            {
                var value = string.IsNullOrEmpty(pairs[1]) ? string.Empty : pairs[1].Replace(KEY_NONE, null).Replace(KEY_UNKNOWN, null).Trim();
                if(sb !=null)
                    sb.Append(value);
                return value;
            }
            return string.Empty;
        }
    }
}